文章目录
  1. 1. Asymmetrical Reading and Writing of Inherited Members
  2. 2. The clone Function

Asymmetrical Reading and Writing of Inherited Members

In classical inheritance, each instance of Author has its own copy of the books array. You could add to it by writing author[1].books.push(‘New Book Title’). That is not initially possible with the object you created using prototypal inheritance because of the way prototype chaining works. A clone is not a fully independent copy of its prototype object; it is a new empty object with its prototype attribute set to the prototype object. When it is just created, author[1].name is actually a link back to the primitive Person.name. This is because of the asymmetry inherent in reading and writing objects linked from the prototype. When you read the value of author[1].name, you are getting the value linked from the prototype, provided you haven’t defined the name attribute directly on the author[1] instance yet. When you write to author[1].name, you are defining a new attribute directly on the author[1] object.
This example illustrates that asymmetry:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var authorClone = clone(Author);
alert(authorClone.name); // Linked to the primative Person.name, which is the
// string 'default name'.
authorClone.name = 'new name'; // A new primative is created and added to the
// authorClone object itself.
alert(authorClone.name); // Now linked to the primative authorClone.name, which
// is the string 'new name'.
authorClone.books.push('new book'); // authorClone.books is linked to the array
// Author.books. We just modified the
// prototype object's default value, and all
// other objects that link to it will now
// have a new default value there.
authorClone.books = []; // A new array is created and added to the authorClone
// object itself.
authorClone.books.push('new book'); // We are now modifying that new array.

This also illustrates why you must create new copies of data types that are passed by reference. In the previous example, pushing a new value onto the authorClone.books array is actually pushing it to Author.books. This is bad because you just modified the value not only for Author but for any object inheriting from Author that has not yet overwritten the default. You must create
new copies of all arrays and objects before you start changing their members. It is very easy to forget this and modify the value of the prototype object. This should be avoided at all costs; debugging these types of errors can be very time-consuming. In these situations, you can use the hasOwnProperty method to distinguish between inherited members and the object’s actual members.

Sometimes prototype objects will have child objects within them. If you want to override a single value within that child object, you have to recreate the entire thing. This can be done by setting the child object to be an empty object literal and then recreating it, but that would mean that the cloned object would have to know the exact structure and defaults for each child object. In order to keep all objects as loosely coupled as possible, any complex child objects should be created using methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var CompoundObject = {
string1: 'default value',
childObject: {
bool: true,
num: 10
}
}
var compoundObjectClone = clone(CompoundObject);
// Bad! Changes the value of CompoundObject.childObject.num.
compoundObjectClone.childObject.num = 5;
// Better. Creates a new object, but compoundObject must know the structure
// of that object, and the defaults. This makes CompoundObject and
// compoundObjectClone tightly coupled.
compoundObjectClone.childObject = {
bool: true,
num: 5
};

In this example, childObject is recreated and ompoundObjectClone.childObject.num is modified. The problem is that compoundObjectClone must know that childObject has two attributes, with values true and 10. A better approach is to have a factory method that creates the childObject:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Best approach. Uses a method to create a new object, with the same structure and
// defaults as the original.
var CompoundObject = {};
CompoundObject.string1 = 'default value',
CompoundObject.createChildObject = function() {
return {
bool: true,
num: 10
}
};
CompoundObject.childObject = CompoundObject.createChildObject();
var compoundObjectClone = clone(CompoundObject);
compoundObjectClone.childObject = CompoundObject.createChildObject();
compoundObjectClone.childObject.num = 5;

The clone Function

1
2
3
4
5
6
/* Clone function. */
function clone(object) {
function F() {}
F.prototype = object;
return new F;
}

First the clone function creates a new and empty function, F. It then sets the prototype attribute of F to the prototype object. You can see here the intent of the original JavaScript creators. The prototype attribute is meant to point to the prototype object, and through prototype chaining it provides links to all the inherited members. Lastly, the function creates a new object by calling the new operator on F. The cloned object that is returned is completely empty, except
for the prototype attribute, which is (indirectly) pointing to the prototype oject, by way of the F object.

文章目录
  1. 1. Asymmetrical Reading and Writing of Inherited Members
  2. 2. The clone Function